#include "Package.h"
#include <limits>
#include "NetUtil.h"
#include "Common.h"

namespace wsbus {

	static const int kChecksumInit = 20211122;
	int Package::newPackageId()
	{
		static int kMaxId = std::numeric_limits<int>::max();
		static std::atomic_int32_t s_id = 1;
		return s_id++ % kMaxId;
	}

	PackagePtr Package::newRequestPackage(const Package::Address &addr, const MessagePtr &msg)
	{
		return newPackage(addr, newPackageId(), PacHeader::eReq, newMoreMessage(msg));
	}

	PackagePtr Package::newRequestPackage(const Address &addr, const std::string &command, const std::string &data)
	{
		return newPackage(addr, newPackageId(), PacHeader::eReq, newMoreMessage(command, data));
	}

	PackagePtr Package::newResponsePackage(const Package::Address &addr, int msgId, const MoreMessagePtr &msg)
	{
		return newPackage(addr, msgId, PacHeader::eRsp, msg);
	}

	PackagePtr Package::newPushPackage(const Package::Address &addr, const MessagePtr &msg)
	{
		return newPackage(addr, newPackageId(), PacHeader::ePush, newMoreMessage(msg));
	}

	PackagePtr Package::newPackage(const Package::Address &addr, int msgId, PacHeader::MsgType msgType, const MoreMessagePtr &mm)
	{
		auto package = std::make_shared<Package>();
		package->header_.timestamp = currentMillisecond();
		package->header_.msgId = msgId;
		package->header_.msgType = msgType;
		package->header_.bodyType = mm->type();
		package->header_.fromAppType = addr.fromAppType;
		package->header_.fromSessionId = addr.fromSessionId;
		package->header_.toAppType = addr.toAppType;
		package->header_.toSessionId = addr.toSessionId;
		package->setBody(mm);
		return package;
	}

	MoreMessagePtr Package::newMoreMessage(const MessagePtr &msg)
	{
		auto mm = std::make_shared<ProtoBase::MoreMessage>();
		mm->set_command(msg->GetTypeName());
		mm->set_type(PacHeader::eProtobuf);
		int msgSize = msg->ByteSizeLong();
		if (msgSize > 0) {
			std::string *data = mm->mutable_data();
			if (data) {
				data->resize(msgSize);
				msg->SerializePartialToArray(&(*data)[0], msgSize);
			}
		}
		return mm;
	}

	MoreMessagePtr Package::newMoreMessage(const std::string &command, const std::string &data)
	{
		auto mm = std::make_shared<ProtoBase::MoreMessage>();
		mm->set_command(command);
		mm->set_type(PacHeader::eJson);
		mm->set_data(data);
		return mm;
	}

	int Package::encode(const PackagePtr &package, beast::flat_buffer &buffer)
	{
		int bodySize = package->getBodySize();
		int packageSize = kPacHeaderSize + bodySize;
		try {
			buffer.prepare(packageSize);
			package->header_.checksum = xcrc32((const unsigned char*)&package->header_, kPacChecksumSize, kChecksumInit);
			char *p = (char*)buffer.data().data();
			memcpy(p, &package->header_, kPacHeaderSize);
			p += kPacHeaderSize;
			package->bodySerializeToArray(p, bodySize);
			LOG(INFO) << "[write] from:" << package->header_.fromAppType << "," << package->header_.fromSessionId
				<< ", to:" << package->header_.toAppType << "," << package->header_.toSessionId
				<< ", msgid:" << package->header_.msgId << ", checksum:" << package->header_.checksum << ", size:" << bodySize;
			return packageSize;
		}
		catch (std::exception &e) {
			LOG(ERROR) << "buffer encode failed, what:" << e.what();
		}
		catch (...) {
			LOG(ERROR) << "buffer encode failed, unkown";
		}
		return 0;
	}

	PackagePtr Package::decode(const beast::flat_buffer &buffer)
	{
		try {
			char *p = (char*)buffer.cdata().data();
			auto package = std::make_shared<Package>();
			memcpy(&package->header_, p, kPacHeaderSize);
			unsigned int newChecksum = xcrc32((const unsigned char*)&package->header_, kPacChecksumSize, kChecksumInit);
			if (newChecksum != package->header_.checksum) {
				LOG(WARNING) << "decode package header checksum failed, old:" << package->header_.checksum << ", new:" << newChecksum;
				return PackagePtr();
			}

			p += kPacHeaderSize;
			auto body = std::make_shared<ProtoBase::MoreMessage>();
			int bodySize = buffer.size() - kPacHeaderSize;
			body->ParseFromArray(p, bodySize);
			package->setBody(body);
			LOG(INFO) << "[read] from:" << package->header_.fromAppType << "," << package->header_.fromSessionId
				<< ", to:" << package->header_.toAppType << "," << package->header_.toSessionId
				<< ", msgid:" << package->header_.msgId << ", checksum:" << package->header_.checksum << ", size:" << bodySize;
			return package;
		}
		catch (std::exception &e) {
			LOG(ERROR) << "buffer decode failed, what:" << e.what();
		}
		catch (...) {
			LOG(ERROR) << "buffer decode failed, unkown";
		}
		return PackagePtr();
	}

	Package::Package() : body_(new MoreMessageWrap())
	{

	}

	Package::~Package()
	{
		delete body_;
	}

	std::string Package::command() const
	{
		static std::string kEmptyString;
		return body_->data ? body_->data->command() : kEmptyString;
	}

	MessagePtr Package::getMessage() const
	{
		if (body_->data && header_.bodyType == PacHeader::eProtobuf) {
			auto msg = createMessage(body_->data->command());
			if (msg && msg->ParseFromArray(body_->data->data().c_str(), body_->data->data().size())) {
				return msg;
			}
		}
		return MessagePtr();
	}

	bool Package::getMessage(std::string &command, std::string &data) const
	{
		if (body_->data && header_.bodyType == PacHeader::eJson) {
			command = body_->data->command();
			data = body_->data->data();
			return true;
		}
		return false;
	}

	void Package::setBody(const MoreMessagePtr &body)
	{
		body_->data = body;
	}

	size_t Package::getBodySize() const
	{
		return body_->data ? body_->data->ByteSizeLong() : 0;
	}

	void Package::bodySerializeToArray(char *&data, int size)
	{
		if (body_->data) {
			body_->data->SerializeToArray(data, size);
		}
	}

}


